/**
 * 
 */
package truerefactor.codesmell;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import truerefactor.graph.AttributeNode;
import truerefactor.graph.ClassNode;
import truerefactor.graph.CodeGraph;
import truerefactor.graph.CodeNode;
import truerefactor.graph.EdgeType;
import truerefactor.graph.MethodNode;
import truerefactor.metrics.CBO;
import truerefactor.metrics.CS;
import truerefactor.metrics.DIT;
import truerefactor.metrics.WMC;
import truerefactor.refactor.Refactoring;
import truerefactor.refactor.RefactoringFactory;

/**
 * LazyClass - Provides an implementation of detection and refactoring sequence
 * decision algorithms for the LazyClass Code Smell.
 * 
 * @author Isaac Griffith
 */
public class LazyClass extends CodeSmell {

    /**
     * Creates a new instance of LazyClass
     */
    public LazyClass()
    {
        this.shortName = "LYCL";
        this.name = "Lazy Class";
        this.description = "";
    }

    /*
     * (non-Javadoc)
     * @see
     * truerefactor.codesmell.CodeSmell#measure(truerefactor.graph.CodeGraph,
     * boolean, java.util.List)
     */
    @Override
    protected double measure(CodeGraph graph, boolean initial, List<List<Refactoring>> sequences)
    {
        // For useless subclasses use collapse hierarchy
        // for classes that do nothing, then use inline class
        double smellFound = 0;
        int numMethods = 0;
        CBO cbo = new CBO();
        CS cs = new CS();

        double cboMedian = cbo.measure(graph);
        double csMedian = cs.measure(graph);

        List<ClassNode> classes = graph.getClasses();

        List<Refactoring> sequence = new LinkedList<Refactoring>();

        for (ClassNode cNode : classes)
        {
            sequence = new LinkedList<Refactoring>();
            numMethods = cNode.getMethods().size();
            double depth = DIT.measure(cNode);

            if (numMethods == 0)
            {
                if (initial)
                {
                    generateInlineClass(cNode, sequence, graph);
                }
                smellFound++;
            }
            else if ((CS.measure(cNode) < csMedian) && ((WMC.measure(cNode) / numMethods) <= 2))
            {
                if (initial)
                {
                    generateInlineClass(cNode, sequence, graph);
                }
                smellFound++;
            }
            else if ((CBO.measure(cNode, graph) < cboMedian) && (depth > 1.0))
            {

                if (initial)
                {
                    ClassNode parent = null;
                    List<String> extnds = cNode.getExtends();
                    List<ClassNode> supers = cNode.getParentClasses();

                    for (String ex : extnds)
                    {
                        for (ClassNode sup : supers)
                        {
                            if (sup.getClassName().equals(extnds))
                            {
                                if (!sup.isInterface())
                                {
                                    parent = sup;
                                    break;
                                }
                            }
                        }
                    }

                    if (parent != null)
                    {
                        CodeNode operands[] = { parent, cNode };
                        sequence.add(RefactoringFactory.getInstance().createCollapseHierarchy(operands));
                    }
                }
                smellFound++;
            }

            if (initial && !sequence.isEmpty())
            {
                sequences.add(sequence);
                // sequence.clear();
            }
        }

        return smellFound;
    }

    /**
     * Generates an Inline Class Refactoring to help eliminate instances of this
     * code smell
     * 
     * @param cNode
     *            ClassNode to be inlined
     * @param sequence
     *            The list of refactorings to add the inline class refactoring
     *            to
     * @param graph
     *            Code Graph representing the current graph being manipulated
     */
    private void generateInlineClass(ClassNode cNode, List<Refactoring> sequence, CodeGraph graph)
    {
        ClassNode target = getHeavyUseClass(cNode, graph);
        sequence.add(RefactoringFactory.getInstance().createInlineClass(cNode, target));
    }

    /**
     * Find the heaviest user of the class represented by cNode
     * 
     * @param cNode
     *            Class to be inlined
     * @param graph
     *            Code Graph representing the current graph being manipulated
     * @return the ClassNode that is the target to inline the class cNode into
     */
    private ClassNode getHeavyUseClass(ClassNode cNode, CodeGraph graph)
    {
        HashMap<ClassNode, Integer> countMap = new HashMap<ClassNode, Integer>();
        for (ClassNode other : graph.getClasses())
        {
            if (graph.isEdge(other, cNode, EdgeType.Association))
                countMap.put(other, 0);
            else if (graph.isEdge(other, cNode, EdgeType.UseDependency))
                countMap.put(other, 0);
            else if (graph.isEdge(other, cNode, EdgeType.Aggregation))
                countMap.put(other, 0);
            else if (graph.isEdge(other, cNode, EdgeType.Composition))
                countMap.put(other, 0);
            else if (graph.isEdge(other, cNode, EdgeType.CollectedAssociation))
                countMap.put(other, 0);
        }

        Set<ClassNode> others = new HashSet<ClassNode>();
        for (ClassNode other : others)
        {
            int count = countMap.get(other);
            List<CodeNode> methods = other.getMethods();
            List<CodeNode> attributes = other.getAttributes();

            for (CodeNode m : methods)
            {
                if (m instanceof MethodNode)
                {
                    MethodNode method = (MethodNode) m;
                    List<MethodNode> assocMethods = method.getOtherMethods();
                    List<AttributeNode> assocAttributes = method.getOtherAttributes();

                    for (MethodNode node : assocMethods)
                    {
                        if (node.getParentClass().equals(cNode))
                            count++;
                    }

                    for (AttributeNode node : assocAttributes)
                    {
                        if (node.getParent().equals(cNode))
                            count++;
                    }
                }
            }

            for (CodeNode a : attributes)
            {
                if (a instanceof AttributeNode)
                {
                    AttributeNode attr = (AttributeNode) a;
                    List<MethodNode> assocMethods = attr.getAssociatedMethods();

                    if (attr.getType().equals(cNode.getClassName()))
                        count++;

                    for (MethodNode method : assocMethods)
                    {
                        if (method.getParentClass().equals(cNode))
                            count++;
                    }
                }
            }

            countMap.put(other, count);
        }

        ClassNode best = null;
        int bestCount = Integer.MIN_VALUE;
        for (ClassNode node : countMap.keySet())
        {
            if (countMap.get(node) > bestCount)
            {
                bestCount = countMap.get(node);
                best = node;
            }
        }

        return best;
    }
}
